package com.rem.designPattern.proxy代理.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;

/**
 * jdk 代理工厂
 * 利用拦截器（必须实现InvocationHandler接口）加上反射机制生成一个代理接口的匿名类，在调用具体方法前调用InvokeHandler来处理
 * 如果想要实现JDK动态代理 那么代理类必须实现接口，否则不能使用
 * <p>
 * 代售点
 *
 * @author Rem
 * @date 2022-11-18
 */
public class ProxyFactory {
    private TrainStation station;

    public void setStation(TrainStation station) {
        this.station = station;
    }

    public SellTickets getProxyObject() {
        /**
         * 返回指定接口的代理类实例 将方法调用调度到指定的调用 处理程序。
         * 形参:
         * loader -定义代理类的类loader
         * interfaces -要实现的proxy类的接口列表
         * h -将方法调用调度到
         * 返回值:
         * 具有指定的类加载程序并实现指定接口的代理类的指定调用处理程序的代理实例
         */
        return (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(), station.getClass().getInterfaces(), new InvocationHandler() {
            /**
             * 处理对代理实例的方法调用并返回结果。当在与其关联的代理实例上调用方法时，将在调用处理程序上调用此方法
             * 形参:
             * proxy -调用方法的代理实例
             * methods -代理实例上调用的接口方法对应的methods实例。方法对象的声明类将是该方法在其中声明的接口，可能是代理类继承方法通过的代理接口的超级接口。
             * args -包含proxy实例中方法调用中传递的参数值的对象数组，如果接口方法不包含参数，则为null。原语类型的参数包在相应的原语包装器类的实例中，例如java.lang.Integer或java.lang.Boolean。
             * 返回值:
             * 从代理实例上的方法调用中返回的值。如果接口方法的声明的返回类型是基元类型，那么此方法返回的值必须是相应的基元包装器类的实例;
             * 否则，它必须是可分配给声明的返回类型的类型。如果该方法返回的值为null ，接口方法的返回类型为primitive，则该方法在代理实例上调用时会抛出NullPointerException。
             * 如果此方法返回的值与上述接口方法声明的返回类型不兼容，则代理实例上的方法调用将引发ClassCastException。
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if ("sell".equals(method.getName())) {
                    System.out.println("代理点收取一些服务费用 1 元---(JDK动态代理方式)");

                    BigDecimal agencyFee = (BigDecimal) args[0];
                    return method.invoke(station, agencyFee.subtract(BigDecimal.ONE));
                }

                return method.invoke(station, args);
            }
        });
    }
}